

#include "SampleService.h"
#include "SampleTasks.h"

#include <sstream>

using namespace ::nebula::sdk;

namespace nebula { namespace sample {

//Each service, which is definitely sub-classes inherited from class ServiceBase,
//	no matter directly or not, should use the macro "REGISTER_SERVICE" in a 
//	proper place (typically, the implementation cpp file), to announce its existence.
//	Otherwise, the framework cannot know the service is there for using, let along 
//	creating its instance.
REGISTER_SERVICE("sample", SampleService);

/*
Note: a service class can be registered for any purposed times, unless their
	names (the string used to identify an instance of particular service) are unique
eg, Only one instance of SampleSerivce named "sample" is registered to framework 
	through the magic macro in this sample. However, client code can register more
	instances instantiates from the same SampleService like the following:
code:
REGISTER_SERVICE("sample-1", SampleService);
REGISTER_SERVICE("sample-2", SampleService);
REGISTER_SERVICE("sample-3", SampleService);
*/

//////////////////////////////////////////////////////////////////////////

SampleService::SampleService() :
	_key_lock(new Mutex),
	_runtime_stat(new RuntimeStat),
	_runtime_stat_lock(new Mutex),
	_encrypt_in_queue(0),
	_encrypt_out_queue(0),
	_decrypt_in_queue(0),
	_decrypt_out_queue(0),
	_encrypt_processor("encrypter"),
	_decrypt_processor("decrypter")
{
}

NEBULA_ERROR_CODE SampleService::OnInitialize()
{
	//register slots
	RegisterUpdateSlot("key",     &KeyUpdateTaskAlloc, &KeyUpdateTaskProc);
	RegisterDeleteSlot("key",     &KeyDeleteProc );
	RegisterPushSlot  ("encrypt", &FileTaskAlloc,      &_encrypt_in_queue);
	RegisterPopSlot   ("encrypt", &_encrypt_out_queue );
	RegisterPushSlot  ("decrypt", &FileTaskAlloc,      &_decrypt_in_queue);
	RegisterPopSlot   ("decrypt", &_decrypt_out_queue );	
	RegisterQuerySlot ("stat", 	  &QueryRunTimeStatProc );

	// set the filter of the done-queues; if no filter set, it behaved like the
	// filter always return true
	_encrypt_out_queue->SetFilter(&SampleService::TaskFilter);
	_decrypt_out_queue->SetFilter(&SampleService::TaskFilter);

	return NEBULA_ERR_SUCCESS;
}

NEBULA_ERROR_CODE SampleService::OnStart()
{
	//start thread pools here
	_encrypt_processor.start(this, &SampleService::ProcEncrpytFile, _encrypt_in_queue, 1, 10);
	_decrypt_processor.start(this, &SampleService::ProcDecrpytFile, _decrypt_in_queue, 1, 10);
	return NEBULA_ERR_SUCCESS;
}

void SampleService::OnStop()
{
	//stop thread pools here
	_encrypt_processor.stop();
	_decrypt_processor.stop();
}

void SampleService::OnFinalize()
{
	//leave empty since there is nothing need to do in this sample
	//Typically, client code should recycle the resouces allocated when OnInitialize()
}

//////////////////////////////////////////////////////////////////////////
ITask* SampleService::KeyUpdateTaskAlloc (IService* service)
{
	/*
	The framework provided input parameter classified as IService* is showed as the pure 
	virtual base class of the concrete service class -- SampleService.
	If it is not the case, there should be a critical mistake in the framework.
	*/
	SampleService* rawservice = dynamic_cast<SampleService*>(service);
	if (!rawservice)
		return 0;
	else
		return rawservice->AllocKeyUpdateTask();
}

NEBULA_ERROR_CODE SampleService::KeyUpdateTaskProc (
	IService* service, IRequest* request, ITask* intask, ITask** outtask)
{
	SampleService* rawservice = dynamic_cast<SampleService*>(service);
	if (!rawservice)
		return NEBULA_ERR_INVALID_ARGUMENT;
	else
		return rawservice->ProcKeyUpdateTask(request, intask, outtask);
}

ITask* SampleService::AllocKeyUpdateTask()
{
	return new EncryptKey;
}

NEBULA_ERROR_CODE SampleService::ProcKeyUpdateTask (
	IRequest* request, ITask* intask, ITask** outtask)
{
	/*The "intask" parameter is definitely an instance of KeyUpdateTask
	since the allocator of the concrete task is registered to framework
	in the early call to SampleService::OnInitialize()
	
	In this sample of Key updating progress, client code leaves the parameter
	"outtask" alone since it need to return nothing to the request*/
	*outtask = 0;

	EncryptKey* rawintask = dynamic_cast<EncryptKey*>(intask);
	if (!rawintask)
	{
		perror("Task raw type not match");
		return NEBULA_ERR_INTERNAL_ERROR;
	}

	if (rawintask->GetContent().empty())
	{
		perror("Invalid data received");
		return NEBULA_ERR_INVALID_ARGUMENT;
	}
	else
	{
		AutoLock lock(_key_lock.get());
		_key.swap(rawintask->GetContent());
		return NEBULA_ERR_SUCCESS;
	}
}


NEBULA_ERROR_CODE SampleService::KeyDeleteProc(IService* service, IRequest* request)
{
	SampleService* rawservice = dynamic_cast<SampleService*>(service);
	if (!rawservice)
		return NEBULA_ERR_INVALID_ARGUMENT;
	else
		return rawservice->ProcKeyDelete(request);
}


NEBULA_ERROR_CODE SampleService::ProcKeyDelete (IRequest* reqeust)
{
	AutoLock lock(_key_lock.get());
	_key.clear();
	return NEBULA_ERR_SUCCESS;
}

//////////////////////////////////////////////////////////////////////////

ITask* SampleService::FileTaskAlloc(IService* service)
{
	SampleService* rawservice = dynamic_cast<SampleService*>(service);
	if (!rawservice)
		return 0;
	else
		return rawservice->AllocFileTask();
}

ITask* SampleService::AllocFileTask()
{
	return new EncryptFile;
}

NEBULA_ERROR_CODE SampleService::ProcEncrpytFile(std::auto_ptr<ITask> task)
{
	/*This member function is treated as a thread pool (TaskProcessor) routine in
	 this sample.
	 The routines need to be functioned in the SDK provided thread pool routine 
	 must be declared and, certainly, implemented following the prototype of thread pool 
	 routine. See TaskProcess.h for detail*/

	pinfo("Begin encrypt file!");

	EncryptFile* ptask = dynamic_cast<EncryptFile*>(task.get());
	if (!ptask)
	{
		perror("Task corrupted!");
		return NEBULA_ERR_INVALID_ARGUMENT;
	}

	std::string key;
	{
		AutoLock lock(_key_lock.get());
		key = _key;
	}

	NEBULA_ERROR_CODE rc = EncryptFileTask(ptask, key);
	if (rc != NEBULA_ERR_SUCCESS)
		return rc;

	/*Note: 
	the input parameter is declared as the type of std::auto_ptr. 
	Such form is carefully considered for the purpose of giving to client code 
	the rights of a thorough task life cycle maintain.
	On one hand, the client do NOT need to care about the resource recycling of
	the task object, the std::auto_ptr do for it.
	On the other hand, the client code could transfer the ownership of the task 
	from itself to an out queue by fulfill a release call upon the std::auto_ptr 
	just like the sample code illustrates as the follow line.
	*/
	return _encrypt_out_queue->AddTask(/*ptask*/ task.release());
}

NEBULA_ERROR_CODE SampleService::ProcDecrpytFile(std::auto_ptr<ITask> task)
{
	EncryptFile* ptask = dynamic_cast<EncryptFile*>(task.get());
	if (!ptask)
		return NEBULA_ERR_INVALID_ARGUMENT;

	DecryptFileTask(ptask);
	return _decrypt_out_queue->AddTask(task.release());
}

NEBULA_ERROR_CODE SampleService::EncryptFileTask(EncryptFile* task, const std::string& key)
{
	//Here user_meta is a point to the interface of IKey to an XML similar way by which
	//the client could easily maintain its own meta data such as configuration and policy.
	//Refer to the KeyValue.h in SDK for detail
	IKey* user_meta = task->GetTaskMeta()->GetUserData();
	if (user_meta->GetBoolValue("info/encrypted"))
	{
		perror("The task has been encrypted!");
		return NEBULA_ERR_INVALID_ARGUMENT;
	}

	if (key.empty())
	{
		perror("No available Key set!");
		return NEBULA_ERR_INVALID_ARGUMENT;
	}

	EncryptString(task->GetContent(), key);

	//write user configure to the meta-data
	user_meta->SetBoolValue("info/encrypted",   true, true);
	user_meta->SetTextValue("configure/key",    key.c_str(), true);
	user_meta->SetTextValue("configure/method", "default", true);

	TaskStatistic(task);

	return NEBULA_ERR_SUCCESS;
}

NEBULA_ERROR_CODE SampleService::DecryptFileTask(EncryptFile* task)
{
	IKey* user_meta = task->GetTaskMeta()->GetUserData();
	if (!user_meta->GetBoolValue("info/encrypted"))
		return NEBULA_ERR_INVALID_ARGUMENT;
		
	const char* key    = user_meta->GetTextValue("configure/key");
	const char* method = user_meta->GetTextValue("configure/method");
	if (!key)
		return NEBULA_ERR_INVALID_ARGUMENT;

	const std::string methodstr = method ? method : "default";
	if (methodstr != "default")
		return NEBULA_ERR_INVALID_ARGUMENT;

	EncryptString(task->GetContent(), key);

	TaskStatistic(task);

	return NEBULA_ERR_SUCCESS;
	
}

void SampleService::EncryptString(std::string& data, const std::string& key)
{
	for (unsigned i = 0; i < data.size(); ++i)
	{
		data[i] ^= key[i % key.size()];
	}
}

bool SampleService::TaskFilter(IService*, const char* dest, const ITask* task_)
{
	ITask        *task      = const_cast<ITask*>(task_);
	ITaskMeta    *meta      = task->GetTaskMeta();
	ITaskJourney *journey   = meta->GetJourney();
	IJourneyNode *generator = journey->GetNodeAt(0);

	// the "task" could be transferred to "dest" if its generator metches "dest"
	if (0 == strcmp(dest, generator->NodeName()))
		return true;
	else
		return false;
}

//////////////////////////////////////////////////////////////////////////
NEBULA_ERROR_CODE SampleService::QueryRunTimeStatProc 
	(IService* service, IRequest* request, ITask** task)
{
	SampleService* rawservice = dynamic_cast<SampleService*>(service);
	if (!rawservice)
		return NEBULA_ERR_INVALID_ARGUMENT;
	else
		return rawservice->ProcQueryRunTimeStat(request, task);
}

NEBULA_ERROR_CODE SampleService::ProcQueryRunTimeStat(IRequest* request, ITask** outtask)
{
	//The "outtask" could be a instance of any kind of sub-class
	//inherited from TaskBase. The framework focuses merely on the pure virtual
	//functions in the super class. Thus, client code could return data to the request
	//by assigning a new instance of RuntimeStatTask to "outtask" as what this sample do 
	//Note: The framework is then in the responsibility of resource maintain of the
	//output task. It will delete the pointer once it handled.
	AutoLock lock(_runtime_stat_lock.get());
	*outtask = new RuntimeStatTask(_runtime_stat.get());
	return NEBULA_ERR_SUCCESS;
}

void SampleService::TaskStatistic(EncryptFile* task)
{
	AutoLock lock(_runtime_stat_lock.get());
	_runtime_stat->Accumulat(task);
}

}} //end of nebula::sample

